package model

import (
	"fmt"
	"go.mongodb.org/mongo-driver/x/mongo/driver/topology"
	"gogame/gameconfig"
	"gogame/lib"
	"gogame/lib/database/mongo"
	"gogame/logger"
	"os"
	"strings"
	"time"
)

type ModelLogManage struct {
	Mdb         *mongo.MongoStruct
	Ver         string         // 版本
	LogChan     chan *ModelLog // 日志chan
	Stop        bool           // 是否已停止写入
	Close       chan bool      // 是否停止写入日志
	WriteSecond time.Duration  // 日志写入间隔
	TableName   string         // 日志写入的表
	MaxNum      int            // 日志单次写入上限
	RetryNum    int            // 失败重试次数
	RetrySecond time.Duration  // 失败重试等待时间
}

func NewModelLogManage(ver string, mdb *mongo.MongoStruct) *ModelLogManage {
	modelLogManage = &ModelLogManage{
		Ver:         ver,
		Mdb:         mdb,
		LogChan:     make(chan *ModelLog, 10000),
		Close:       make(chan bool),
		WriteSecond: 2,
		TableName:   "model_log",
		MaxNum:      10000,
		RetryNum:    5,
		RetrySecond: 60,
	}
	// 内网减少次数和时间
	if gameconfig.ServerConfig.GetGameVer() == "debug" {
		modelLogManage.RetryNum = 1
		modelLogManage.RetrySecond = 3
	}

	// 开始处理
	go modelLogManage.StartWriteModelLog()

	return modelLogManage
}

var modelLogManage *ModelLogManage
var modelLogCrossManage *ModelLogManage

// InitModelLogManage 初始化modelLogManage
func InitModelLogManage(ver string) *ModelLogManage {
	switch ver {
	case "cross":
		modelLogCrossManage = NewModelLogManage(ver, CrossMdb)
		return modelLogCrossManage
	default:
		modelLogManage = NewModelLogManage(ver, Mdb)
		return modelLogManage
	}
}

// StopWriteModelLog 停止写入modelLog
func (m *ModelLogManage) StopWriteModelLog() {
	if m.Stop {
		return
	}
	m.Stop = true
	m.Close <- true

	// Close可能在执行间隔中触发，需要再执行一次，防止数据漏入库
	m.DoHandle()
	close(m.LogChan)
	logger.Print("%s modelLog stop write!!", m.Ver)
}

// StartWriteModelLog 写入modelLog
func (m *ModelLogManage) StartWriteModelLog() {
	_ticker := time.NewTicker(time.Second * m.WriteSecond)
	defer _ticker.Stop()

	logger.Print("%s modelLog start write!!", m.Ver)
	for {
		select {
		// x秒执行一次
		case <-_ticker.C:
			m.DoHandle()
		// 停止写入
		case <-m.Close:
			return
		}
	}

}

// DoHandle 开始处理modelLog
func (m *ModelLogManage) DoHandle() {
	// 开始整合modelLog
	_insertList := m.GetModelLogList()
	// 插入model_log
	m.InsertsModelLogList(_insertList)
}

// GetModelLogList 获取modelLogList
func (m *ModelLogManage) GetModelLogList() []any {
	var _insertList []any

	// 存在未消费的modelLog
	if len(m.LogChan) > 0 {
		for modelLog := range m.LogChan {
			if modelLog == nil {
				break
			}
			_insertList = append(_insertList, modelLog)
			_logLen := len(m.LogChan)
			// 取出所有一次性处理 or 单次超过xx条
			if _logLen == 0 || _logLen >= m.MaxNum {
				break
			}
		}
	}

	return _insertList
}

// InsertsModelLogList modelLog入库
func (m *ModelLogManage) InsertsModelLogList(insertList []any) {
	if len(insertList) == 0 {
		return
	}

	_retryNum := 0
	// 插入model_log
	for num := 0; num <= m.RetryNum; num++ {
		_, err := m.Mdb.InsertMany(m.TableName, insertList)
		// 插入成功
		if err == nil {
			break
		}

		_, topologyServerSelectionError := err.(topology.ServerSelectionError)
		_, topologyConnectionError := err.(topology.ConnectionError)
		_remoteFail := strings.Contains(err.Error(), " An existing connection was forcibly closed by the remote host")
		// 超时 or 远程主机关闭 才重试
		if topologyServerSelectionError || topologyConnectionError || _remoteFail {
			if num <= m.RetryNum-1 {
				_retryNum++
				logger.WorkerLog.Warn(
					fmt.Sprintf(
						"modellog.InsertsModelLogList retryNum-->%d  logLen-->%d\nerr-->%s",
						_retryNum, len(insertList), err.Error(),
					),
				)

				_timer := time.NewTimer(time.Second * m.RetrySecond)
				select {
				case <-_timer.C:
					_timer.Stop()
				}
			}

		} else {
			// 逐条写入，有问题的log抛出
			for _, data := range insertList {
				_, err := m.Mdb.InsertOne(m.TableName, data)
				_idDupKey := strings.Contains(err.Error(), "_id_ dup key")
				if err != nil && !_idDupKey {
					logger.WorkerLog.Warn(fmt.Sprintf("modellog InsertOne fail!\nerr ->%s", err.Error()))
				}
			}
			break
		}
	}

	// 达到重试上限，写入文件
	if _retryNum >= m.RetryNum {
		m.WriteLogByFile(insertList)
	}
}

// WriteLogByFile 日志写入文件
func (m *ModelLogManage) WriteLogByFile(insertList []any) {
	_folderPath := lib.GetFilePath("file_mongo_log")
	// 创建目录
	lib.CreateFolder(_folderPath)

	_fileName := fmt.Sprintf("modellog_%d.txt", lib.Time.Now())
	_filePath := fmt.Sprintf("%s/%s", _folderPath, _fileName)
	_file, err := lib.OpenFile(_filePath, os.O_APPEND)
	if err != nil {
		logger.WorkerLog.Warn(fmt.Sprintf("modellog.WriteLogByFile os.Open fail!\nerr ->%s", err.Error()))
		return
	}
	defer _file.Close()

	for _, data := range insertList {
		_file.Write(lib.Dumps(data))
		_file.WriteString("\n")
	}
	logger.WorkerLog.Warn(fmt.Sprintf("modellog.WriteLogByFile success! file ->%s", _fileName))
}

type ModelLog struct {
	Id    string `json:"_id" bson:"_id"` // mongo _id
	Uid   string `json:"uid"`            // 玩家uid
	Act   string `json:"act"`            // 操作
	Table string `json:"table"`          // 表名
	D     []any  `json:"d"`              // 参数
}

// SaveModelLog model日志入库
func SaveModelLog(ver string, modelLog *ModelLog) {
	switch ver {
	case "cross":
		modelLogCrossManage.LogChan <- modelLog
	default:
		modelLogManage.LogChan <- modelLog
	}
}
